import test from "ava"
import ceno from "../src"
import { HttpError, TimeoutError } from "../src"
import { createHttpTestServer } from "./helpers/create-http-test-server"

test('重试机制 - 基本重试成功', async (t) => {
    t.plan(3)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/retry', (request, response) => {
        attemptCount++
        if (attemptCount < 3) {
            response.status(500).json({ error: 'Internal Server Error' })
        } else {
            response.json({ success: true, attemptCount })
        }
    })

    interface Result {
        success: boolean
        attemptCount: number
    }

    const data = await ceno.get<Result>(server.url + '/retry', {
            retry: {
                limit: 3,
                methods: ['get'],
                statusCodes: [500]
        }
    }).json()

    t.is(data.success, true)
    t.is(data.attemptCount, 3)
    t.is(attemptCount, 3)

    await server.close()
})

test('重试机制 - 数字形式的重试配置', async (t) => {
    t.plan(2)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/retry', (request, response) => {
        attemptCount++
        if (attemptCount < 3) {
            response.status(500).json({ error: 'Internal Server Error' })
        } else {
            response.json({ success: true, attemptCount })
        }
    })

    interface Result {
        success: boolean
        attemptCount: number
    }

    const data = await ceno.get<Result>(server.url + '/retry', {
        retry: 3 // 数字形式
    }).json()

    t.is(data.success, true)
    t.is(attemptCount, 3)

    await server.close()
})

test('重试机制 - 达到重试限制后抛出错误', async (t) => {
    t.plan(3)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/retry-fail', (request, response) => {
        attemptCount++
        response.status(500).json({ error: 'Internal Server Error', attemptCount })
    })

    const error = await t.throwsAsync(
        () => ceno.get(server.url + '/retry-fail', {
            retry: {
                limit: 2,
                methods: ['get'],
                statusCodes: [500]
            }
        }),
        { instanceOf: HttpError }
    )

    t.is(error.response.status, 500)
    t.is(attemptCount, 3) // 初次请求 + 2次重试

    await server.close()
})

test('重试机制 - 允许重试的方法', async (t) => {
    t.plan(2)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/retry', (request, response) => {
        attemptCount++
        if (attemptCount < 3) {
            response.status(500).json({ error: 'Server Error' })
        } else {
            response.json({ success: true, attemptCount })
        }
    })

    interface Result {
        success: boolean
        attemptCount: number
    }

    const data = await ceno.get<Result>(server.url + '/retry', {
        retry: {
            limit: 3,
            methods: ['get'], // GET 方法允许重试
            statusCodes: [500]
        }
    }).json()

    t.is(data.success, true)
    t.is(attemptCount, 3)

    await server.close()
})

test('重试机制 - 不允许重试的方法', async (t) => {
    t.plan(3)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.post('/retry', (request, response) => {
        attemptCount++
        response.status(500).json({ error: 'Server Error', attemptCount })
    })

    const error = await t.throwsAsync(
        () => ceno.post(server.url + '/retry', {
            json: { test: 'data' },
            retry: {
                limit: 3,
                methods: ['get'], // POST 方法不在允许列表中
                statusCodes: [500]
            }
        }),
        { instanceOf: HttpError }
    )

    t.is(error.response.status, 500)
    t.is(attemptCount, 1) // 只执行一次，不重试

    await server.close()
})

test('重试机制 - 默认重试方法', async (t) => {
    const retryableMethods = ['get', 'put', 'head', 'delete']
    const server = await createHttpTestServer()

    for (const method of retryableMethods) {
        let attemptCount = 0
        
        server[method](`/${method}`, (request, response) => {
            attemptCount++
            if (attemptCount < 2) {
                response.status(500).json({ error: 'Server Error' })
            } else {
                // 为 HEAD 方法特殊处理，因为它不应该有响应体
                if (method === 'head') {
                    response.status(200).end()
                } else {
                    response.json({ success: true, method })
                }
            }
        })

        if (method === 'head') {
            const response = await ceno[method](server.url + `/${method}`, {
                retry: { limit: 2, statusCodes: [500] }
            })
            t.is(response.status, 200)
        } else {
            const data = await ceno[method](server.url + `/${method}`, {
                retry: { limit: 2, statusCodes: [500] }
            }).json()
            t.is(data.success, true)
            t.is(data.method, method)
        }
    }

    await server.close()
})

test('重试机制 - 允许重试的状态码', async (t) => {
    const retryStatusCodes = [408, 429, 500, 502, 503, 504]
    const server = await createHttpTestServer()

    for (const statusCode of retryStatusCodes) {
        let attemptCount = 0
        
        server.get(`/${statusCode}`, (request, response) => {
            attemptCount++
            if (attemptCount < 2) {
                response.status(statusCode).json({ error: `Error ${statusCode}` })
            } else {
                response.json({ success: true, statusCode })
            }
        })

        interface Result {
            success: boolean
            statusCode: number
        }

        const data = await ceno.get<Result>(server.url + `/${statusCode}`, {
            retry: { limit: 2, statusCodes: [statusCode] }
        }).json()

        t.is(data.success, true)
        t.is(data.statusCode, statusCode)
    }

    await server.close()
})

test('重试机制 - 不允许重试的状态码', async (t) => {
    t.plan(3)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/not-retryable', (request, response) => {
        attemptCount++
        response.status(404).json({ error: 'Not Found', attemptCount })
    })

    const error = await t.throwsAsync(
        () => ceno.get(server.url + '/not-retryable', {
            retry: {
                limit: 3,
                methods: ['get'],
                statusCodes: [500] // 404 不在重试列表中
            }
        }),
        { instanceOf: HttpError }
    )

    t.is(error.response.status, 404)
    t.is(attemptCount, 1) // 只执行一次，不重试

    await server.close()
})

test('重试机制 - 413 状态码不重试', async (t) => {
    t.plan(3)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/payload-too-large', (request, response) => {
        attemptCount++
        response.status(413).json({ error: 'Payload Too Large', attemptCount })
    })

    const error = await t.throwsAsync(
        () => ceno.get(server.url + '/payload-too-large', {
            retry: {
                limit: 3,
                methods: ['get'],
                statusCodes: [413] // 即使配置了413，也不会重试
            }
        }),
        { instanceOf: HttpError }
    )

    t.is(error.response.status, 413)
    t.is(attemptCount, 1) // 413状态码强制不重试

    await server.close()
})

test('重试机制 - Retry-After 头部（秒数）', async (t) => {
    t.plan(2)

    let requestCount = 0
    const server = await createHttpTestServer()

    server.get('/retry-after-seconds', (request, response) => {
        requestCount++
        if (requestCount === 1) {
            response.set('Retry-After', '1') // 1秒后重试
            response.status(429).json({ error: 'Too Many Requests' })
        } else {
            response.json({ success: true, requestCount })
        }
    })

    const startTime = Date.now()
    const data = await ceno.get(server.url + '/retry-after-seconds', {
        retry: {
            limit: 2,
            methods: ['get'],
            statusCodes: [429],
            afterStatusCodes: [429]
        }
    }).json()

    const elapsedTime = Date.now() - startTime
    
    t.deepEqual(data, { success: true, requestCount: 2 })
    t.true(elapsedTime >= 1000) // 应该至少等待1秒

    await server.close()
})

test('重试机制 - Retry-After 头部（日期格式）', async (t) => {
    let requestCount = 0
    const server = await createHttpTestServer()

    server.get('/retry-after-date', (request, response) => {
        requestCount++
        if (requestCount === 1) {
            const retryDate = new Date(Date.now() + 800)
            response.set('Retry-After', retryDate.toUTCString())
            response.status(503).json({ error: 'Service Unavailable' })
        } else {
            response.json({ success: true, requestCount })
        }
    })

    interface Result {
        success: boolean
        requestCount: number
    }

    const data = await ceno.get<Result>(server.url + '/retry-after-date', {
        retry: {
            limit: 2,
            methods: ['get'],
            statusCodes: [503],
            afterStatusCodes: [503]
        }
    }).json()
    
    // 只测试重试是否工作，不测试精确时间
    t.is(data.success, true)
    t.is(data.requestCount, 2)

    await server.close()
})

test('重试机制 - 替代的重试头部', async (t) => {
    const headers = ['RateLimit-Reset', 'X-RateLimit-Reset', 'X-Rate-Limit-Reset']
    const server = await createHttpTestServer()

    for (const headerName of headers) {
        let requestCount = 0
        
        server.get(`/retry-${headerName.toLowerCase()}`, (request, response) => {
            requestCount++
            if (requestCount === 1) {
                response.set(headerName, '1') // 1秒后重试
                response.status(429).json({ error: 'Rate Limited' })
            } else {
                response.json({ success: true, header: headerName })
            }
        })

        interface Result {
            success: boolean
            header: string
        }
        
        const startTime = Date.now()
        const data = await ceno.get<Result>(server.url + `/retry-${headerName.toLowerCase()}`, {
            retry: {
                limit: 2,
                methods: ['get'],
                statusCodes: [429],
                afterStatusCodes: [429]
            }
        }).json()

        const elapsedTime = Date.now() - startTime
        
        t.is(data.success, true)
        t.is(data.header, headerName)
        t.true(elapsedTime >= 1000)
    }

    await server.close()
})

test('重试机制 - maxRetryAfter 限制（实际行为测试）', async (t) => {
    let requestCount = 0
    const server = await createHttpTestServer()

    server.get('/max-retry-after', (request, response) => {
        requestCount++
        if (requestCount <= 2) { // 前两次请求都失败
            response.set('Retry-After', '5') // 5秒后重试
            response.status(429).json({ error: 'Too Many Requests', requestCount })
        } else {
            response.json({ success: true, requestCount })
        }
    })

    interface Result {
        success: boolean
        requestCount: number
    }

    // 根据实际实现，maxRetryAfter 会限制延迟时间，但仍然会重试
    const startTime = Date.now()
    const data = await ceno.get<Result>(server.url + '/max-retry-after', {
        retry: {
            limit: 2,
            methods: ['get'],
            statusCodes: [429],
            afterStatusCodes: [429],
            maxRetryAfter: 2000 // 最大等待2秒，会限制到2秒而不是5秒
        }
    }).json()
    const elapsedTime = Date.now() - startTime

    t.is(data.success, true)
    t.is(data.requestCount, 3)
    // 应该等待大约 2000ms * 2 = 4000ms（两次重试，每次限制到2秒）
    t.true(elapsedTime >= 4000)
    t.true(elapsedTime < 6000) // 不应该等待10秒（2次5秒）

    await server.close()
})

test('重试机制 - maxRetryAfter 为 undefined 时的默认行为', async (t) => {
    let requestCount = 0
    const server = await createHttpTestServer()

    server.get('/no-max-retry-after', (request, response) => {
        requestCount++
        if (requestCount === 1) {
            response.set('Retry-After', '1') // 1秒后重试
            response.status(503).json({ error: 'Service Unavailable' })
        } else {
            response.json({ success: true, requestCount })
        }
    })

    interface Result {
        success: boolean
        requestCount: number
    }

    const startTime = Date.now()
    const data = await ceno.get<Result>(server.url + '/no-max-retry-after', {
        retry: {
            limit: 2,
            methods: ['get'],
            statusCodes: [503],
            afterStatusCodes: [503]
            // maxRetryAfter 未设置，应该使用 after 的值
        }
    }).json()
    const elapsedTime = Date.now() - startTime

    t.is(data.success, true)
    t.is(data.requestCount, 2)
    t.true(elapsedTime >= 1000) // 应该等待1秒

    await server.close()
})

test('重试机制 - 自定义延迟函数', async (t) => {
    t.plan(4)

    let attemptCount = 0
    const delays: number[] = []
    const server = await createHttpTestServer()

    server.get('/custom-delay', (request, response) => {
        attemptCount++
        if (attemptCount < 3) {
            response.status(500).json({ error: 'Server Error' })
        } else {
            response.json({ success: true, attemptCount })
        }
    })

    interface Result {
        success: boolean
        attemptCount: number
    }

    const startTime = Date.now()
    const data = await ceno.get<Result>(server.url + '/custom-delay', {
        retry: {
            limit: 3,
            methods: ['get'],
            statusCodes: [500],
            delay: (attemptCount) => {
                const delay = attemptCount * 200 // 线性增长: 200ms, 400ms
                delays.push(delay)
                return delay
            }
        }
    }).json()

    const elapsedTime = Date.now() - startTime
    
    t.is(data.success, true)
    t.is(attemptCount, 3)
    t.deepEqual(delays, [200, 400]) // 两次重试的延迟
    t.true(elapsedTime >= 600) // 至少等待 200 + 400 = 600ms

    await server.close()
})

test('重试机制 - 默认指数退避延迟', async (t) => {
    t.plan(3)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/exponential-backoff', (request, response) => {
        attemptCount++
        if (attemptCount < 3) {
            response.status(500).json({ error: 'Server Error' })
        } else {
            response.json({ success: true, attemptCount })
        }
    })

    interface Result {
        success: boolean
        attemptCount: number
    }

    const startTime = Date.now()
    const data = await ceno.get<Result>(server.url + '/exponential-backoff', {
        retry: {
            limit: 3,
            methods: ['get'],
            statusCodes: [500]
            // 使用默认延迟函数: 0.3 * (2 ** (attemptCount - 1)) * 1000
        }
    }).json()

    const elapsedTime = Date.now() - startTime
    
    t.is(data.success, true)
    t.is(attemptCount, 3)
    // 默认延迟: 300ms (第1次重试) + 600ms (第2次重试) = 900ms
    t.true(elapsedTime >= 900)

    await server.close()
})

test('重试机制 - backoffLimit 限制', async (t) => {
    t.plan(4)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/backoff-limit', (request, response) => {
        attemptCount++
        if (attemptCount < 3) {
            response.status(500).json({ error: 'Server Error' })
        } else {
            response.json({ success: true, attemptCount })
        }
    })

    interface Result {
        success: boolean
        attemptCount: number
    }

    const startTime = Date.now()
    const data = await ceno.get<Result>(server.url + '/backoff-limit', {
        retry: {
            limit: 3,
            methods: ['get'],
            statusCodes: [500],
            backoffLimit: 100, // 最大延迟100ms
            delay: () => 1000 // 尝试延迟1000ms，但会被限制到100ms
        }
    }).json()

    const elapsedTime = Date.now() - startTime
    
    t.is(data.success, true)
    t.is(attemptCount, 3)
    t.true(elapsedTime >= 200) // 两次重试，每次最多100ms
    t.true(elapsedTime < 500) // 应该远小于2000ms

    await server.close()
})

test('重试机制 - TimeoutError 不重试', async (t) => {
    t.plan(3)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/timeout-no-retry', (request, response) => {
        attemptCount++
        setTimeout(() => {
            response.end('Hello Ceno')
        }, 2000) // 2秒延迟
    })

    const error = await t.throwsAsync(
        () => ceno.get(server.url + '/timeout-no-retry', {
            timeout: 500, // 500ms 超时
            retry: {
                limit: 3,
                methods: ['get'],
                statusCodes: [500] // 即使配置了重试，TimeoutError也不重试
            }
        }),
        { instanceOf: TimeoutError }
    )

    t.is(error.name, 'TimeoutError')
    t.is(attemptCount, 1) // 只执行一次，不重试

    await server.close()
})

test('重试机制 - throwHttpErrors: false 不影响重试（当有错误时）', async (t) => {
    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/retry-no-throw', (request, response) => {
        attemptCount++
        if (attemptCount < 3) {
            response.status(500).json({ error: 'Server Error' })
        } else {
            response.json({ success: true, attemptCount })
        }
    })

    // throwHttpErrors: false 时，重试仍然需要错误被抛出才能触发
    // 但是在当前实现中，如果不抛出错误，就不会进入重试逻辑
    const response = await ceno.get(server.url + '/retry-no-throw', {
            retry: {
                limit: 3,
                methods: ['get'],
                statusCodes: [500]
            },
        throwHttpErrors: false // 这会阻止重试，因为没有错误抛出
    })

    const data = await response.json()

    // 当 throwHttpErrors: false 时，实际上不会重试
    t.is(response.status, 500)
    t.is(attemptCount, 1) // 只执行一次，因为没有错误抛出来触发重试

    await server.close()
})

test('重试机制 - throwHttpErrors: false 最终失败不抛错', async (t) => {
    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/retry-fail-no-throw', (request, response) => {
        attemptCount++
        response.status(500).json({ error: 'Server Error', attemptCount })
    })

    interface Result {
        error: string
        attemptCount: number
    }

    const response = await ceno.get<Result>(server.url + '/retry-fail-no-throw', {
        retry: {
            limit: 2,
                methods: ['get'],
                statusCodes: [500]
            },
            throwHttpErrors: false
    })

    const data = await response.json()

    t.is(response.status, 500)
    t.false(response.ok)
    t.is(data.error, 'Server Error')
    t.is(data.attemptCount, 1) // throwHttpErrors: false 阻止了重试

    await server.close()
})

test('重试机制 - 复杂的 Retry-After 场景', async (t) => {
    let requestCount = 0
    const server = await createHttpTestServer()

    server.get('/complex-retry-after', (request, response) => {
        requestCount++
        if (requestCount === 1) {
            // 第一次：使用秒数格式
            response.set('Retry-After', '1')
            response.status(429).json({ error: 'Rate Limited' })
        } else if (requestCount === 2) {
            // 第二次：使用更短的等待时间
            response.set('Retry-After', '1')
            response.status(503).json({ error: 'Service Unavailable' })
        } else {
            response.json({ success: true, requestCount })
        }
    })

    interface Result {
        success: boolean
        requestCount: number
    }

    const startTime = Date.now()
    const data = await ceno.get<Result>(server.url + '/complex-retry-after', {
        retry: {
            limit: 3,
            methods: ['get'],
            statusCodes: [429, 503],
            afterStatusCodes: [429, 503]
        }
    }).json()

    const elapsedTime = Date.now() - startTime
    
    t.is(data.success, true)
    t.is(data.requestCount, 3)
    t.true(elapsedTime >= 2000) // 至少等待 1000 + 1000 = 2000ms

    await server.close()
})

test('重试机制 - 成功响应立即返回', async (t) => {
    t.plan(2)

    let attemptCount = 0
    const server = await createHttpTestServer()

    server.get('/immediate-success', (request, response) => {
        attemptCount++
        response.json({ success: true, attemptCount })
    })

    interface Result {
        success: boolean
        attemptCount: number
    }

    const data = await ceno.get<Result>(server.url + '/immediate-success', {
        retry: {
            limit: 3,
            methods: ['get'],
            statusCodes: [500]
        }
    }).json()

    t.is(data.success, true)
    t.is(attemptCount, 1) // 成功响应，不需要重试

    await server.close()
})